﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using log4net;

namespace Interactive.Utility.Collections{
    /// <summary>
    /// 可回收的容器
    /// </summary>
    public class RecycleDic<K, V>{
        #region 常量

        /// <summary>
        /// 日志记录器
        /// </summary>
        private static readonly ILog myLog = LogManager.GetLogger("RecycleDicK_V");

        #endregion

        #region 变量

        /// <summary>
        /// 向外界表明繁忙,获取失败,有一个线程正在做
        /// </summary>
        public readonly V DefaultBusyValue;

        /// <summary>
        /// 默认的获取方法
        /// </summary>
        private readonly Func<K, V> DefaultFunc;

        /// <summary>
        /// 容量
        /// </summary>
        private readonly int capacity;

        /// <summary>
        /// 容器
        /// </summary>
        private readonly Dictionary<K, CacheWrapper> dictionary;

        /// <summary>
        /// 回收模式,默认LFU
        /// 两种算法
        /// Less Frequently Used (LFU)（最近最不常使用（LFU））
        /// Least Recently Used (LRU) (最近最久被使用（LRU）)
        /// </summary>
        private readonly bool isLru;

        /// <summary>
        /// 锁
        /// </summary>
        private readonly object myLock = new object();

        /// <summary>
        /// 回收时的容量
        /// </summary>
        private readonly int recycleCapacity;


        /// <summary>
        /// 回收量
        /// </summary>
        private readonly int recycleNum;


        /// <summary>
        /// 保证永远只有一个线程在执行回收方法
        /// </summary>
        private readonly object timerLock = new object();


        private readonly string typeName;

        /// <summary>
        /// 在等待获取值时的等待时间
        /// </summary>
        private readonly int waitMillseconds;

        /// <summary>
        /// 是否在回收
        /// </summary>
        private bool isClear;

        #endregion

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="capacity">容量</param>
        /// <param name="defaultFunc">获取的方法</param>
        /// <param name="defaultBusyValue">标识繁忙的值</param>
        /// <param name="waitMillseconds">等待时间,默认50毫秒</param>
        /// <param name="isLru">是否是LRU算法,默认LFU</param>
        public RecycleDic(int capacity, Func<K, V> defaultFunc, V defaultBusyValue, int waitMillseconds,
                          bool isLru){
            this.capacity = capacity;
            dictionary = new Dictionary<K, CacheWrapper>(this.capacity);
            recycleCapacity = (int) Math.Ceiling(capacity*0.9);

            recycleNum = (int) Math.Ceiling(capacity*0.1);
            DefaultBusyValue = defaultBusyValue;
            DefaultFunc = defaultFunc;
            this.waitMillseconds = waitMillseconds;
            this.isLru = isLru;

            isClear = false;
            typeName = "类型K:" + typeof (K) + ",类型V:" + typeof (V);
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="capacity">容量</param>
        /// <param name="defaultFunc">获取的方法</param>
        /// <param name="defaultBusyValue">标识繁忙的值</param>
        public RecycleDic(int capacity, Func<K, V> defaultFunc, V defaultBusyValue)
            : this(capacity, defaultFunc, defaultBusyValue, 50, false){
        }

        public V this[K key]{
            get{
                CacheWrapper cacheWrapper;
                if (dictionary.TryGetValue(key, out cacheWrapper)) return cacheWrapper.GetValue(key);
                return default(V);
            }
            set{
                CacheWrapper temp;
                if (!dictionary.TryGetValue(key, out temp)){
                    lock (myLock){
                        if (!dictionary.TryGetValue(key, out temp)){
                            temp = new CacheWrapper(this, value);
                            if (dictionary.Count > recycleCapacity && !isClear)
                                ThreadPool.UnsafeQueueUserWorkItem(ob => ClearDic(), null);
                            dictionary.Add(key, temp);
                            return;
                        }
                    }
                }
                temp.EnforceRefresh(value);
            }
        }

        public int Count{
            get { return dictionary.Count; }
        }

        /// <summary>
        /// 回收
        /// </summary>
        private void ClearDic(){
            if (!isClear){
                lock (timerLock){
                    if (!isClear) isClear = true;
                    else return;
                }
            }
            else return;

            myLog.Error("开始回收内存了!" + typeName);

            try{
                var list = new List<CounterStru>(dictionary.Count + 5);
                lock (myLock){
                    list.AddRange(from keyValue in dictionary
                                  let temp = keyValue.Value
                                  select new CounterStru(keyValue.Key, temp.Counter, temp.LastGetTime));
                }
                if (isLru) list.Sort(CounterStru.CompareTo);
                else list.Sort();

                for (int i = 0; i < recycleNum && i < dictionary.Count; i++){
                    Remove(list[i].Key);
                }
            }
            catch (Exception exception){
                myLog.Error("在回收内存的时候有错误产生!" + typeName, exception);
            }
            finally{
                lock (timerLock){
                    isClear = false;
                }
            }
        }

        /// <summary>
        /// 刷新缓存
        /// </summary>
        /// <param name="k">键</param>
        public void EnforceRefresh(K k){
            CacheWrapper temp;
            if (dictionary.TryGetValue(k, out temp)) temp.EnforceRefresh();
        }

        /// <summary>
        /// 刷新缓存,用默认值
        /// </summary>
        /// <param name="k">键</param>
        /// <param name="defaultValue">默认值</param>
        public void EnforceRefresh(K k, V defaultValue){
            this[k] = defaultValue;
        }

        /// <summary>
        /// 获取值
        /// </summary>
        /// <param name="k">键</param>
        /// <returns>值</returns>
        public V GetValue(K k){
            return this[k];
        }

        /// <summary>
        /// 获取值,如果isMust=Ture,代表一定要获取到值,当不存在的时候
        /// 重新建立该项
        /// </summary>
        /// <param name="key">键</param>
        /// <param name="isMust">是否一定要获取到值,true代表是,反之</param>
        /// <returns>值</returns>
        public V GetValue(K key, bool isMust){
            CacheWrapper cacheWrapper;
            if (dictionary.TryGetValue(key, out cacheWrapper))
                return cacheWrapper.GetValue(key);
            if (isMust){
                lock (myLock){
                    if (!dictionary.TryGetValue(key, out cacheWrapper)){
                        cacheWrapper = new CacheWrapper(this);


                        //如果达到警戒,不等到5分钟后就执行
                        if (dictionary.Count > recycleCapacity && !isClear)
                            ThreadPool.UnsafeQueueUserWorkItem(ob => ClearDic(), null);

                        dictionary.Add(key, cacheWrapper);
                    }
                }
                return cacheWrapper.GetValue(key);
            }
            return default(V);
        }

        /// <summary>
        /// 是否存在
        /// </summary>
        /// <param name="key">键</param>
        /// <returns>结果</returns>
        public bool ContainsKey(K key){
            return dictionary.ContainsKey(key);
        }

        /// <summary>
        /// 添加一个键,值对,当存在该键时会报错
        /// </summary>
        /// <param name="key">键</param>
        /// <param name="value">值</param>
        /// <exception cref="ArgumentException">当键存在时会抛出该异常</exception>
        public void Add(K key, V value){
            CacheWrapper temp;
            if (!dictionary.TryGetValue(key, out temp)){
                lock (myLock){
                    if (!dictionary.TryGetValue(key, out temp)){
                        temp = new CacheWrapper(this, value);


                        //如果达到警戒,不等到5分钟后就执行
                        if (dictionary.Count >= recycleCapacity && !isClear)
                            ThreadPool.UnsafeQueueUserWorkItem(ob => ClearDic(), null);

                        dictionary.Add(key, temp);
                    }
                    else throw new ArgumentException("对不起,已经存在该key");
                }
            }
            else
                throw new ArgumentException("对不起,已经存在该key");
        }

        /// <summary>
        /// 移除
        /// </summary>
        /// <param name="key">键</param>
        /// <returns>是否成功</returns>
        public bool Remove(K key){
            lock (myLock){
                return dictionary.Remove(key);
            }
        }

        /// <summary>
        /// 尝试获取
        /// </summary>
        /// <param name="key">键</param>
        /// <param name="value">值</param>
        /// <returns>是否有</returns>
        public bool TryGetValue(K key, out V value){
            CacheWrapper cacheWrapper;
            bool result = dictionary.TryGetValue(key, out cacheWrapper);
            value = result ? cacheWrapper.GetValue(key) : default(V);
            return result;
        }

        #region Nested type: CacheWrapper

        /// <summary>
        /// 包装器
        /// </summary>
        private class CacheWrapper{
            /// <summary>
            /// 锁
            /// </summary>
            private readonly object myLock = new object();

            /// <summary>
            /// 依赖项
            /// </summary>
            private readonly RecycleDic<K, V> recycleDic;

            /// <summary>
            /// 缓存值
            /// </summary>
            private V cacheValue;

            /// <summary>
            /// 计时器
            /// </summary>
            private int counter;

            /// <summary>
            /// 是否已经初始化值
            /// </summary>
            private bool isGet;

            /// <summary>
            /// 上次使用的时间
            /// </summary>
            private DateTime lastGetTime;

            /// <summary>
            /// 构造函数,不会立刻初始化值
            /// </summary>
            /// <param name="recycleDic">依赖项</param>
            public CacheWrapper(RecycleDic<K, V> recycleDic) : this(recycleDic, true, default(V)){
            }

            /// <summary>
            /// 构造函数,用给定的值进行初始化
            /// </summary>
            /// <param name="recycleDic">依赖项</param>
            /// <param name="initValue">初始值</param>
            public CacheWrapper(RecycleDic<K, V> recycleDic, V initValue) : this(recycleDic, false, initValue){
            }

            /// <summary>
            /// 构造函数
            /// </summary>
            /// <param name="recycleDic">依赖项</param>
            /// <param name="isLazyGet">是否是懒初始化,true是,false否</param>
            /// <param name="initValue">当为否的时候需要提供初始值</param>
            private CacheWrapper(RecycleDic<K, V> recycleDic, bool isLazyGet, V initValue){
                this.recycleDic = recycleDic;
                if (!isLazyGet){
                    cacheValue = initValue;
                    isGet = true;
                }
                lastGetTime = DateTime.Now;
                counter = 0;
            }

            /// <summary>
            /// 计数器
            /// </summary>
            public int Counter{
                get { return counter; }
            }

            /// <summary>
            /// 上次获取时间
            /// </summary>
            public DateTime LastGetTime{
                get { return lastGetTime; }
            }


            public V GetValue(K k){
                Notify();
                if (!isGet){
                    if (Monitor.TryEnter(myLock, recycleDic.waitMillseconds)){
                        try{
                            if (!isGet){
                                cacheValue = recycleDic.DefaultFunc(k);
                                isGet = true;
                            }
                        }

                        catch (Exception exception){
                            myLog.Error("在获取缓存值的时候出现错误!", exception);
                        }
                        finally{
                            Monitor.Exit(myLock);
                        }
                    }
                    else return recycleDic.DefaultBusyValue;
                }
                return cacheValue;
            }

            #region 强制刷新缓存

            public void EnforceRefresh(){
                Notify();
                if (isGet){
                    lock (myLock){
                        if (isGet)
                            isGet = false;
                    }
                }
            }

            /// <summary>
            /// 更新计数器
            /// </summary>
            private void Notify(){
                Interlocked.Increment(ref counter);
                lastGetTime = DateTime.Now;
            }

            public void EnforceRefresh(V defaultValue){
                Notify();
                lock (myLock){
                    isGet = true;
                    cacheValue = defaultValue;
                }
            }

            #endregion
        }

        #endregion

        #region Nested type: CounterStru

        /// <summary>
        /// 回收的时候会用到,有两种比较算法
        /// </summary>
        private struct CounterStru : IComparable<CounterStru>{
            private readonly int Counter;
            public readonly K Key;
            private readonly DateTime LastGetTime;


            public CounterStru(K key, int counter, DateTime lastGetTime){
                Key = key;
                Counter = counter;
                LastGetTime = lastGetTime;
            }

            #region Implementation of IComparable<RecycleDic<K,V>.CounterStru>

            /// <summary>
            /// 默认比较方式,使用LFU算法
            /// </summary>
            /// <param name="other">要比较的对象</param>
            /// <returns></returns>
            public int CompareTo(CounterStru other){
                int result = Counter - other.Counter;
                if (result == 0)
                    return LastGetTime.CompareTo(other.LastGetTime);
                return result;
            }

            /// <summary>
            /// 采用LRU算法
            /// </summary>
            /// <param name="self">自己</param>
            /// <param name="other">要比较的对象</param>
            /// <returns></returns>
            public static int CompareTo(CounterStru self, CounterStru other){
                int result = self.LastGetTime.CompareTo(other.LastGetTime);
                if (result == 0) return self.Counter - other.Counter;
                return result;
            }

            #endregion
        }

        #endregion
    }
}